﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Windows.Threading;
using BorgExt.Tracing;
using Properties;

namespace BorgExt
{
    public class Runtime
    {
        public event EventHandler ShutdownStarted;
        public event EventHandler ShutdownFinished;
        public event ExceptionEventHandler UnhandledException;

        internal ExtensionType StartupExtensionType { get; private set; }
        internal Extension StartupExtension { get; private set; }
        internal object LockObject { get; set; }
        internal DiscoveryCore DiscoveryCore { get; private set; }

        internal Notifier Notifier { get; private set; }
        public Logger Logger { get; private set; }
        public TypeStore TypeStore { get; private set; }
        public Composer Composer { get; private set; }
        public InstanceStore InstanceStore { get; private set; }
        public Qualifier Qualifier { get; private set; }

        public ReadOnlyCollection<string> AssemblyDirectories { get; private set; }
        public Dictionary<string, string> Arguments { get; private set; }
        public VerbosityLevel VerbosityLevel { get; private set; }
        public Dispatcher Dispatcher { get; private set; }
        public DateTime StartupTime { get; private set; }

        public static string VersionInfo
        {
            get
            {
                var builder = new StringBuilder();
                var executingAsm = Assembly.GetExecutingAssembly();

                builder.AppendFormat("{0} Version {1}{2}",
                    AssemblyAnalyzer.GetAssemblyTitle(executingAsm),
                    AssemblyAnalyzer.GetAssemblyFileVersion(executingAsm),
                    Environment.NewLine);

                builder.AppendFormat("[{0} Version {1}]{2}",
                    AssemblyAnalyzer.GetAssemblyProduct(executingAsm),
                    AssemblyAnalyzer.GetAssemblyInformationalVersion(executingAsm),
                    Environment.NewLine);

                builder.AppendLine(AssemblyAnalyzer.GetAssemblyCopyright(executingAsm));

                return builder.ToString();
            }
        }

        internal Runtime(RuntimeConfig config)
        {
            LockObject = new object();
            TypeStore = config.TypeStore ?? new TypeStore();
            Composer = config.Composer ?? new Composer();
            InstanceStore = config.InstanceStore ?? new InstanceStore();
            DiscoveryCore = config.DiscoveryCore ?? new DiscoveryCore();
            Qualifier = config.Qualifier ?? new Qualifier();
            Notifier = config.Notifier ?? new Notifier();
            Logger = config.Logger ?? new Logger();

            TypeStore.Init(this);
            Composer.Init(this);
            InstanceStore.Init(this);
            DiscoveryCore.Init(this);
            Qualifier.Init(this);
            Notifier.Init(this);
            Logger.Init(this);

            if (config.TrackCalls)
                Composer.ExtensionObjectFactory =
                    LoggingActivatorExtensionObjectFactory.CreateExtensionObject;
            
            Arguments = new Dictionary<string, string>();

            if (config.Arguments != null)
                foreach (var pair in config.Arguments)
                    Arguments[pair.Key] = pair.Value;

            AssemblyDirectories = config.AssemblyDirectories != null
                ? new ReadOnlyCollection<string>(config.AssemblyDirectories)
                : new ReadOnlyCollection<string>(new string[0]);

            VerbosityLevel = config.VerbosityLevel;
            AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
        }

        internal void Start(ICollection<Contract> contracts, ICollection<Plugin> plugins,
            ExtensionType startup, IEnumerable<LogEventArgs> initialLogs)
        {
            Dispatcher = Dispatcher.CurrentDispatcher;

            Dispatcher.ShutdownStarted += OnShutdownStarted;
            Dispatcher.ShutdownFinished += OnShutdownFinished;
            Dispatcher.UnhandledException += OnDispatcherUnhandledException;

            StartupTime = DateTime.Now;

            LogStartup(initialLogs);
            DiscoveryCore.AssemblyDiscovered(new DiscoveryEventArgs(contracts, plugins));

            StartupExtensionType = startup;
            StartupExtension = Composer.GetShared(StartupExtensionType);

            ((IStartup)StartupExtension.Activate()).Start(DiscoveryCore);
        }

        public void Shutdown()
        {
            Dispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
        }

        internal void OnUnhandledException(object sender, ExceptionEventArgs args)
        {
            if (UnhandledException != null)
            {
                UnhandledException(args);

                if (!args.IsTerminating && args.IsHandled)
                    return;
            }

            Logger.OnLog(
                VerbosityLevel.Minimal,
                LogType.Error,
                this,
                Messages.Runtime_UnhandledException,
                (object) AppDomain.CurrentDomain.FriendlyName,
                args.ExceptionObject);
        }

        private void OnShutdownStarted(object sender, EventArgs e)
        {
            if (ShutdownStarted != null)
                ShutdownStarted(this, EventArgs.Empty);
            
            Composer.Destroy(StartupExtension);
        }

        private void OnShutdownFinished(object sender, EventArgs e)
        {
            LogShutdown();

            if (ShutdownFinished == null)
                return;
            
            ShutdownFinished(this, EventArgs.Empty);
        }

        private void LogStartup(IEnumerable<LogEventArgs> startupLogs)
        {
            var builder = new StringBuilder();

            builder.AppendLine("--------------------------------------------------------");
            builder.Append("Runtime started: ");
            builder.AppendLine(StartupTime.ToString(CultureInfo.InvariantCulture));
            builder.Append("--------------------------------------------------------");

            Logger.OnLog(this, builder.ToString(), new object[0]);

            if (startupLogs == null)
                return;

            foreach (var args in startupLogs)
                Logger.OnLog(VerbosityLevel.Minimal, this, args);
        }

        private void LogShutdown()
        {
            var builder = new StringBuilder();
            var now = DateTime.Now;
            var timeSpan = now - StartupTime;

            builder.AppendLine("--------------------------------------------------------");
            builder.Append("Runtime finished: ");
            builder.AppendLine(now.ToString(CultureInfo.InvariantCulture));
            builder.AppendFormat("Uptime: {0:d2}:{1:d2}:{2:d2}", timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
            builder.AppendLine();
            builder.AppendLine("--------------------------------------------------------");
            
            Logger.OnLog(this, builder.ToString(), new object[0]);
        }

        private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            OnUnhandledException(sender, new ExceptionEventArgs(e.ExceptionObject, e.IsTerminating));
        }

        private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            OnUnhandledException(sender, new ExceptionEventArgs(e.Exception, false));
            e.Handled = true;
        }
    }
}

